﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Com.FastSearch.SharePoint.Interfaces;
using Com.FastSearch.SharePoint.UCF;
using Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI.Client;
using Com.FastSearch.Utils;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using DataFormWebPart=Microsoft.SharePoint.WebPartPages.WebPart;


// FastSearch...

namespace Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI
{
    /// <summary>
    /// Base class for all ESP Search web parts.<br/>
    /// Search SearchRequest for web parts are grouped by the "Cross Web Part Query ID" setting.<br/>
    /// <br/>   
    /// The Search Lifecycle is like the following:<br/>
    /// <code>
    /// OnInit  --> ConfigureSearch  --> CreateChildControls  --> SetSearchResult
    /// </code>
    /// If one extends this webpart the OnInit and CreateChildControls of this parent class have to be called.<br/>
    /// OnInit is calling ConfigureSearch which is used to write the WebPart custom properties into the IESPSearchSettings (SearchRequest)<br/>
    /// which again is used to perform the search at a later stage.<br/>
    /// CreateChildControl is calling the UCF.Search which again is executing the SetSearchResult which contains the search results.<br/>
    /// <br/>
    /// Each Web Part can be placed in a so called Search Group. <br/>
    /// A search group is used to have mulitple different searches on one page where as exactly one search per search group is performed.    <br/>
    /// <br/>
    /// <code>
    /// WebPart1(group1) -
    /// WebPart2(group1)  |-> SearchRequest (Search Configuration group1) --> Search1
    /// WebPart3(group1) -                                                       |
    ///        ^                                                                 |
    ///        |------------------------------------------------------------------
    /// 
    /// 
    /// WebPart4(group2) -
    /// WebPart5(group2)  |-> SearchRequest (Search Configuration group2) --> Search2
    /// WebPart6(group2) -                                                       |
    ///        ^                                                                 |
    ///        |------------------------------------------------------------------
    /// </code>
    /// Each Web Part group must contain exactly one of the ESPSearchConfig (or a child of it) which configures the core search
    /// parameter like qrserver connection, view and other default search settings.
    /// 
    /// </summary>
    public abstract class ESPSearchWebPart : Microsoft.SharePoint.WebPartPages.DataFormWebPart, IESPSearchPartGUI
    {
        /// <summary>
        /// The title of the configuration category in which all search configuration is exposed.
        /// </summary>
        protected const string _CATEGORY_STR = "Search Configuration";

        /// <summary>
        /// The search group for which a configuration web part is available. -1 if none.
        /// </summary>
        protected int _configWebPartAvailable = -1;

        /// <summary>
        /// Design-time color coding for different search groups.
        /// </summary>
        protected static readonly string[] ZoneDesigntimeColors =
        {
            "#F7E294", "#FFB56B", "#E87720", "#593F25", "#453F29", "#335577"
        };

        /// <summary>
        /// Constructor. Initializes the search factory.
        /// </summary>
        protected ESPSearchWebPart()
        {
            // Initialize the Search Factory if not initialized yet            
            ESPSearchPart.InitDefaultSearchFactory();
        }
      
        /// <summary>
        /// The search group of which the web part is a member. The default
        /// group is <see cref="ESPSearchGroupEnum.Group0"/>.
        /// </summary>
        /// <seealso cref="ESPSearchGroupEnum"/>
        [Category("Advanced Search configuration"),
         DefaultValue(ESPSearchGroupEnum.Group0),
         Personalizable(PersonalizationScope.Shared),
         WebBrowsable(true),
         WebDisplayName("Search Group ID"),
         WebDescription("")]
        public ESPSearchGroupEnum ESPSearchGroupID
        {
            get;
            set;
        }

        /// <summary>
        /// Shared instance of use case facade which performs the search and calls back to the SetSearchResult<br/>
        /// once the results are returned.
        /// <see cref="ESPSearchPart.Search"/>
        /// </summary>
        protected ESPSearchPart UCF
        {
            get
            {
                if (HttpContext.Current.Items["UCF" + ESPSearchGroupID] == null)
                {
                    HttpContext.Current.Items["UCF" + ESPSearchGroupID] = new ESPSearchPart();
                }
                return (ESPSearchPart) HttpContext.Current.Items["UCF" + ESPSearchGroupID];
            }
        }

        /// <summary>
        /// SearchRequest for the search. The SearchRequest should be populated by the child web parts in the abstract ConfigureSearch method.<br/>
        /// This Interface contains all the search parameters which later are used to perform the search. <see cref="ESPSearchPart.Search"/>
        /// The SearchRequest is kept in the Request Scope and is used to configure the search acrosswep parts. (One per search Group)        
        /// </summary>
        public IESPSearchSettings SearchRequest
        {
            get
            {
                if (HttpContext.Current.Items["SR" + (int) ESPSearchGroupID] == null)
                {
                    HttpContext.Current.Items["SR" + (int) ESPSearchGroupID] =
                        new SearchRequest(ESPSearchGroupID, HttpContext.Current.Request);
                }
                return (IESPSearchSettings) HttpContext.Current.Items["SR" + (int) ESPSearchGroupID];
            }
        }

        /// <summary>
        /// By default an empty stylesheet is returned. Override in child classes if xsl transformations
        /// are needed. Use <see cref="LoadXslFromAssembly"/> to load embedded stylesheets.
        /// </summary>
        /// <returns>An inline xsl stylesheet</returns>
        public virtual string GetEmbeddedXsl()
        {
            // By default an empty stylesheet is returned
            // Override in child classes if output is needed by using xsl
            return  "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" >" +
                 "</xsl:stylesheet>";
        }

        /// <summary>
        /// Load an embadded xslt file from the Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.dll within the xslt folder.
        /// </summary>
        /// <param name="xsl">the name of the embadded xslt file within the xslt project folder</param>
        /// <returns>the xslt file content as string</returns>
        protected static string LoadXslFromAssembly(string xsl)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            // Get the Xsl out of the assembly - Mark Xsl as an Embedded resource for this to work.
            Stream stream = assembly.GetManifestResourceStream("Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.xslt." + xsl);
            StreamReader r = new StreamReader(stream);
            return r.ReadToEnd();
        }

        /// <summary>
        /// Get Access to the IESPSearchManager.<br/>
        /// NOTE: Do not perform searches direclty on the IESPSearchManager and use the official life cycle as described in the clsss description instead.
        /// But can be used to access view configurations, sort fields and navigator configurations.        
        /// </summary>
        /// <returns>Returns the <see cref="IESPSearchManager"/></returns>
        protected IESPSearchManager GetSearchManager()
        {
            IESPSearchManagerFactory fact = SimpleFactoryManager<IESPSearchManagerFactory>.Factory;
            return string.IsNullOrEmpty(SearchRequest.ESPConnection)
                   ? fact.Create(SPContext.Current.Site.WebApplication)
                   : fact.Create(SearchRequest.ESPConnection);
        }

        /// <summary>
        /// On Init calls it's parent first which loads the web part properties.<br/>
        /// After that if calls the <see cref="ConfigureSearch"/> method which is used to set the <see cref="SearchRequest"/> parameters 
        /// for this search group. <br></br>
        /// Each web part can add additional parameters to the search request but one has to make sure that parameters are not overwritten otherwise the search results
        /// may be incosistent depending on which web part is called first.<br></br>
        /// <br/>
        /// Also the first time the web part is loadded (added to the page) the <see cref="GetEmbeddedXsl"/> method is called which 
        /// is used to set the xslt. by default an stylesheet with no output is used.<br/>
        /// <br/>
        /// Exceptions are catched an the Exception Message is shown in the web part body.
        /// </summary>
        /// <param name="e"><see cref="EventArgs"/> of the SharePoint lifecycle</param>
        [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            ChromeType = PartChromeType.None;

            ConfigureSearch();

            // Get initial xsl if needed
            try
            {
                if (Xsl == null)
                {
                    Xsl = GetEmbeddedXsl();
                }
            }
            catch (Exception ex)
            {
                Controls.Add(new LiteralControl("ERROR: " + ex.Message));
            }
        }

        /// <summary>
        /// Is using the <see cref="ESPSearchPart.Search"/> to perform the search by using the <see cref="SearchRequest"/> for the current search group.<br/>
        /// <b>Note:</b>Also it checks that exactly one of the ESPSearchConfig (or one of it's children) is part of the current search group, which is needed to perform a succesful search as 
        /// the ESPSearchConfig contains the essential search configuration properties to perform a search.<br/>
        /// <br/>
        /// Exceptions are catched an the Exception Message is shown in the web part body.
        /// </summary>
        [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
        [AspNetHostingPermission(SecurityAction.LinkDemand,
                    Level = AspNetHostingPermissionLevel.Minimal)]
        protected override void CreateChildControls()
        {
            try
            {
                UCF.Search(SearchRequest, this);
            }
            catch (Exception ex)
            {
                Controls.Add(new LiteralControl(ex.Message));
                //Controls.Add(new LiteralControl("<br><br>This Exception most probably occurs because the connection to the ESP QRServer(s) has not been configured correctly or the configured view does no longer exists."));
                //Controls.Add(new LiteralControl("<br><a href=\"/_layouts/ESPConnectionConfig/ESPConnectionConfig.aspx\" >The connection can be configured in \"Site Settings \" -> \"Site Collection Administration\" -> \"ESP Query Server Connections\"</a> or in the WebPart properties containing the ESP Config data!"));
            }

            // Check if an ESPSearchConfig for this CrossWebPartID is on the page.
            if (WebPartManager != null && WebPartManager.DisplayMode != WebPartManager.BrowseDisplayMode)
            {
                // Check if a ESP Config web part or one of it's parents exists for the current
                // Search Group id
                _configWebPartAvailable =
                    WebPartHelper.CheckForESPConfigWebPartTypeOnPage(WebPartManager, ESPSearchGroupID);
            }
            base.CreateChildControls();
        }

        /// <summary>
        /// Writes some design time Information to the webpart if we are not in browse mode.
        /// Mainly embadds the content into a colored dashed frame (different color per search group) and shows the search group id
        /// this web part is belonging to.
        /// </summary>
        /// <param name="writer"><see cref="HtmlTextWriter"/></param>
        protected void WriteWebPartDesingModeInfoFrameStart(HtmlTextWriter writer)
        {
            writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "dashed");
            writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "medium");
            writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, ZoneDesigntimeColors[(int) ESPSearchGroupID]);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.RenderBeginTag(HtmlTextWriterTag.Span);
            writer.Write("This info is only visible on design time!<br/><br/>");
            writer.Write("Search Web Part for Group <b>" + ESPSearchGroupID + "</b><br>");
        }

        /// <summary>
        /// End tags to close the frame in design mode
        /// </summary>
        /// <param name="writer"><see cref="HtmlTextWriter"/></param>
        protected static void WriteWebPartDesingModeInfoFrameEnd(HtmlTextWriter writer)
        {
            writer.RenderEndTag();
            writer.RenderEndTag();
        }

        /// <summary>
        /// Warning message if the current web part does contain none or more of the ESPSearchConfig web parts in the same search group.
        /// Multiple web parts of this type will mix up the search depending on which one of the <see cref="ESPSearchConfig"/> (or one of it's children) is loaded first.
        /// If no <see cref="ESPSearchConfig"/> is available the search will fail as the core search configuration is missing.
        /// <see cref="ESPSearchWebPart"/> for more info on search groups.
        /// </summary>
        /// <param name="writer"><see cref="HtmlTextWriter"/></param>
        protected virtual void WriteWebPartDesingModeInfo(HtmlTextWriter writer)
        {
            if (_configWebPartAvailable == -1)
            {
                writer.Write(
                    "<b>Information:</b> This webpart is not in a Search Group which contains any ESP Configuration Web Part");
                writer.Write("Please add a WebPart into the same search group id which contains the ESP Configuration.");
                writer.Write("This is either a Search List or a Hidden ESP Config Search Web Part");
            }
            else if (_configWebPartAvailable > 0)
            {
                writer.Write(
                    "<b>Information:</b> There are multiple webparts which configure the ESP Search for this Search Group!<br>");
                writer.Write(
                    "Please make sure there is only one of \"ESP Config\" or \"ESP Search List\" per Search Group.");
            }
        }
       
        /// <summary>
        /// Must be implemented by the child class to set the search parameters into the <see cref="SearchRequest"/> which is later used to perform the search.
        /// <example>
        /// Move/copy the needed web part custom parameters into the SearchRequest object.
        /// </example>
        /// <see cref="ESPSearchWebPart"/> for the lifecycle of the search.                
        /// </summary>
        protected abstract void ConfigureSearch();


        /// <summary>
        /// Display some desing time info to the web part if not in browse mode.
        /// The displayed info like search group and warning on wrong configuration (multiple or none of <see cref="ESPSearchConfig"/>)
        /// should make it easier to set up the search web parts.
        /// </summary>
        /// <param name="writer"><see cref="HtmlTextWriter"/></param>
        protected override void Render(HtmlTextWriter writer)
        {
            if (WebPartManager != null && WebPartManager.DisplayMode != WebPartManager.BrowseDisplayMode)
            {
                WriteWebPartDesingModeInfoFrameStart(writer);
                WriteWebPartDesingModeInfo(writer);
                WriteWebPartDesingModeInfoFrameEnd(writer);
            }
            base.Render(writer);
        }

        /// <summary>
        /// Is called after the search has been performed by the <see cref="ESPSearchPart"/> once the search has been performed.
        /// <see cref="ESPSearchWebPart"/> for details on the search life cycle.
        /// </summary>
        /// <param name="SearchResult"><see cref="IESPSearchResult"/> containing the search result for the current search group</param>
        public abstract void SetSearchResult(IESPSearchResult SearchResult);
    }
}
